package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.redis.cache.GuiguCache;
import com.spzx.product.api.domain.Product;
import com.spzx.product.api.domain.ProductDetails;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.SkuQuery;
import com.spzx.product.api.domain.vo.SkuLockVo;
import com.spzx.product.api.domain.vo.SkuPrice;
import com.spzx.product.api.domain.vo.SkuStockVo;
import com.spzx.product.domain.SkuStock;
import com.spzx.product.mapper.ProductDetailsMapper;
import com.spzx.product.mapper.ProductMapper;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.mapper.SkuStockMapper;
import com.spzx.product.service.IProductService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 商品Service业务层处理
 */
@Slf4j
@Service
//@Transactional
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private ProductSkuMapper productSkuMapper;

    @Autowired
    private ProductDetailsMapper productDetailsMapper;

    @Autowired
    private SkuStockMapper skuStockMapper;


    @Autowired
    RedissonClient redissonClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<Product> selectProductList(Product product) {
        return productMapper.selectProductList(product);
    }

    @Transactional //事务4个特性。  ACID
    @Override
    public int insertProduct(Product product) {
        //1.保存product
        productMapper.insert(product); //注意:主键回填

        //2.保存List<ProductSku>
        List<ProductSku> productSkuList = product.getProductSkuList();
        int size = productSkuList.size();
        for (int i = 0; i < size; i++) {
            ProductSku productSku = productSkuList.get(i);  //数据来自表单
            //数据来自业务层手动封装
            productSku.setSkuCode(product.getId() + "_" + i);
            productSku.setSkuName(product.getName() + " " + productSku.getSkuSpec());
            productSku.setProductId(product.getId());
            productSku.setStatus(0);
            productSkuMapper.insert(productSku); //注意:主键回填

            //3.保存SkuStock
            SkuStock skuStock = new SkuStock();
            skuStock.setSkuId(productSku.getId());
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setLockNum(0);
            skuStock.setAvailableNum(skuStock.getTotalNum());
            skuStock.setSaleNum(0);
            skuStockMapper.insert(skuStock);
        }

        //4.保存ProductDetails
        ProductDetails productDetails = new ProductDetails();
        productDetails.setProductId(product.getId());
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetailsMapper.insert(productDetails);

        return 1;
    }


    @Override
    public Product selectProductById(Long id) {
        //1.查询Product对象，将其他数据都封装在product中一起返回
        Product product = productMapper.selectById(id);

        //2.根据商品id查询List<ProductSku>
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id));
        product.setProductSkuList(productSkuList);

        List<Long> productSkuIdList = productSkuList.stream().map(ProductSku::getId).collect(Collectors.toList());

        //根据productSkuIdList集合,查询SkuStock集合
        List<SkuStock> skuStockList = skuStockMapper.selectList(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, productSkuIdList));
        Map<Long, Integer> skuToTotalNumMap = skuStockList.stream().collect(Collectors.toMap(SkuStock::getSkuId, SkuStock::getTotalNum));

        productSkuList.forEach(productSku -> {
            //3.查询每一个sku对应库存数据
            productSku.setStockNum(skuToTotalNumMap.get(productSku.getId()));
        });

        //4.商品详情图片ProductDetails
        ProductDetails productDetails = productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, id));
        product.setDetailsImageUrlList(Arrays.asList(productDetails.getImageUrls().split(",")));
        return product;
    }

    @Transactional
    @Override
    public int updateProduct(Product product) {
        //1.修改Product
        productMapper.updateById(product);

        //2.修改List<ProductSku>
        List<ProductSku> productSkuList = product.getProductSkuList();
        productSkuList.forEach(productSku -> {
            productSkuMapper.updateById(productSku);

            //3.修改List<SkuStock>
            SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, productSku.getId()));
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setAvailableNum(skuStock.getTotalNum() - skuStock.getLockNum());
            skuStockMapper.updateById(skuStock);
        });

        //4.修改ProductDetails
        ProductDetails productDetails = productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, product.getId()));
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetailsMapper.updateById(productDetails);

        return 1;
    }

    @Transactional
    @Override
    public int deleteProductByIds(Long[] ids) {
        List<Long> productIds = Arrays.asList(ids);
        //1.删除商品Product
        productMapper.deleteBatchIds(productIds);

        //2.删除List<ProductSku>
        List<ProductSku> productSkuList = productSkuMapper.selectList(
                new LambdaQueryWrapper<ProductSku>().in(ProductSku::getProductId, productIds).select(ProductSku::getId));
        //只要skuIds
        List<Long> productSkuIdList = productSkuList.stream().map(ProductSku::getId).collect(Collectors.toList());
        productSkuMapper.deleteBatchIds(productSkuIdList);

        //3.删除List<SkuStock>
        skuStockMapper.delete(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, productSkuIdList));

        //4.删除详情图片
        productDetailsMapper.delete(new LambdaQueryWrapper<ProductDetails>().in(ProductDetails::getProductId, productIds));

        return 1;
    }


    @Override
    public void updateAuditStatus(Long id, Integer auditStatus) {
        Product product = new Product();
        product.setId(id);

        if (auditStatus == 1) {
            product.setAuditStatus(1);
            product.setAuditMessage("审批通过");
        } else {
            product.setAuditStatus(-1);
            product.setAuditMessage("审批拒绝");
        }
        productMapper.updateById(product);
    }

    @Override
    public void updateStatus(Long id, Integer status) {
        Product product = new Product();
        product.setId(id);

        if (status == 1) { //上架
            product.setStatus(1);

            RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("sku:bloom:filter");

            //商品上架将商品标识增加到布隆过滤器中。 商品上架，那么对应所有sku都需要上架，即：都需要存放到布隆过滤器中。
            List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>()
                    .eq(ProductSku::getProductId, id));
            productSkuList.forEach(sku -> {
                bloomFilter.add(sku.getId());
            });


        } else { //下架
            product.setStatus(-1);
        }
        productMapper.updateById(product);
    }

    @Override
    public List<ProductSku> getTopSkuSale() {
        return productSkuMapper.getTopSkuSale();
    }


    @Override
    public List<ProductSku> skuList(SkuQuery skuQuery) {
        return productSkuMapper.skuList(skuQuery);
    }


    //=====详情 开始=====================================================



   /* @Override
    public ProductSku getProductSku(Long id) {
        try {
            //1.先从缓存查数据，命中数据，直接返回。
            String dataKey = "product:sku:"+id; //  冒号可以在视图工具上显示key的层级关系。父子节点。
            ProductSku productSku = (ProductSku)redisTemplate.opsForValue().get(dataKey);
            if(productSku!=null){
                log.info("命中缓存，直接返回，线程ID：{}，线程名称：{}",Thread.currentThread().getId(),Thread.currentThread().getName());
                return productSku;
            }
            //2.如果缓存没有命中数据，才会获取分布式锁，从数据库中加载数据。
            String lockKey = dataKey+":lock";
            String lockValue = UUID.randomUUID().toString().replaceAll("-","");
            //2.1获取分布式锁
            Boolean ifAbsent = this.redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS);
            if(ifAbsent){ //获取锁成功
                try {
                    log.info("获取锁成功：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                    //2.2执行业务逻辑
                    productSku = getSkuFromDB(id);

                    long ttl = productSku == null ? 1 * 60 : 10 * 60;

                    if(productSku == null){
                        productSku = new ProductSku();
                    }
                    //数据为空，也存放到缓存中（时间设置比较短时间就行，不要和正常数据设置一样过期时间。空数据无意义），缓解缓存穿透问题。但是不能解决随机值问题。后续采用布隆过滤器来解决。
                    this.redisTemplate.opsForValue().set(dataKey,productSku,ttl,TimeUnit.SECONDS);
                    return productSku;
                } finally {
                    //2.3释放分布式锁
                    //4.业务执行完毕释放锁
                    String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    redisScript.setScriptText(scriptText);
                    redisScript.setResultType(Long.class);
                    redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockValue);
                }

            }else{//获取锁失败
                //2.4获取分布式锁失败，睡觉，再重试（递归，自旋）
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {

                }
                return getProductSku(id);
            }

        } catch (Exception e) {
            //3.兜底补偿方案：有异常情况，从数据库获取数据返回。
            log.error("兜底补偿方案：有异常情况，从数据库获取数据返回");
            return getSkuFromDB(id);
        }
    }*/

   /* private ProductSku getSkuFromDB(Long id) {
        return productSkuMapper.selectById(id);
    }*/


    //目标方法：使用切面类对其功能增强。业务代码和非业务代码解耦。
    @GuiguCache(prefix = "productSku:")
    @Override
    public ProductSku getProductSku(Long id) {
        return productSkuMapper.selectById(id);
    }

    @GuiguCache(prefix = "product:")
    @Override
    public Product getProduct(Long id) {
        return productMapper.selectById(id);
    }

    @GuiguCache(prefix = "skuPrice:")
    @Override
    public SkuPrice getSkuPrice(Long id) {
        ProductSku productSku = productSkuMapper.selectById(id);
        SkuPrice skuPrice = new SkuPrice();
        skuPrice.setSkuId(productSku.getId());
        skuPrice.setSalePrice(productSku.getSalePrice());
        skuPrice.setMarketPrice(productSku.getMarketPrice());
        return skuPrice;
    }

    @GuiguCache(prefix = "productDetails:")
    @Override
    public ProductDetails getProductDetails(Long productId) {
        return productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, productId));
    }

    @GuiguCache(prefix = "skuSpecValue:")
    @Override
    public Map<String, Long> getSkuSpecValue(Long productId) {
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>()
                .eq(ProductSku::getProductId, productId)
                .select(ProductSku::getSkuSpec, ProductSku::getId));
        Map<String, Long> skuSpecValueMap = new Hashtable<>();
        productSkuList.forEach(sku -> {
            skuSpecValueMap.put(sku.getSkuSpec(), sku.getId());
        });
        return skuSpecValueMap;
    }

    @GuiguCache(prefix = "skuStock:")
    @Override
    public SkuStockVo getSkuStock(Long skuId) {
        SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>()
                .eq(SkuStock::getSkuId, skuId));
        SkuStockVo skuStockVo = new SkuStockVo();
        skuStockVo.setSkuId(skuStock.getSkuId());
        skuStockVo.setSaleNum(skuStock.getSaleNum());
        skuStockVo.setAvailableNum(skuStock.getAvailableNum());
        return skuStockVo;
    }

    //=====详情 结束=====================================================


    @Override
    public List<SkuPrice> getSkuPriceList(List<Long> skuIdList) {
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>()
                .in(ProductSku::getId, skuIdList)
                .select(ProductSku::getId, ProductSku::getSalePrice));// select id,sale_price from product_sku

        return productSkuList.stream().map(item -> {
            SkuPrice skuPrice = new SkuPrice();
            skuPrice.setSkuId(item.getId());
            skuPrice.setSalePrice(item.getSalePrice());
            return skuPrice;
        }).collect(Collectors.toList());
    }

    @Transactional
    @Override
    public String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList) {
        String key = "sku:checkAndLock:" + orderNo;
        String dataKey = "sku:lock:data:" + orderNo;

        //1.业务去重
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if (!ifAbsent) {
            if (redisTemplate.hasKey(dataKey)) { //数据存在，说明已经锁定库存了。
                return "";  //第一次请求过来，已经干完活。第二请求，获取不到锁，数据已经有了，无需干活了。
            } else {
                return "重复提交";
            }
        }

        //2.检查库存
        skuLockVoList.forEach(skuLockVo -> {
            //增加行锁，不能被别的事务查，也不能被别的事务改。注意：不是锁库存。
            SkuStock skuStock = skuStockMapper.check(skuLockVo.getSkuId(), skuLockVo.getSkuNum()); //其实是查询操作，并且增加行锁。
            if (skuStock == null) { //数据不存在，或库存不够
                skuLockVo.setIsHaveStock(false);
            } else {
                skuLockVo.setIsHaveStock(true);
            }
        });

        //3.只要有一个商品没有库存，那么锁定库存失败
        if (skuLockVoList.stream().anyMatch(skuLockVo -> !skuLockVo.getIsHaveStock())) {
            //将没有库存的商品信息告诉调用者
            StringBuffer result = new StringBuffer();
            List<SkuLockVo> noHaveSkuLockVoList = skuLockVoList.stream().filter(skuLockVo -> !skuLockVo.getIsHaveStock()).toList();
            for (SkuLockVo skuLockVo : noHaveSkuLockVoList) {
                //解除去重
                redisTemplate.delete(key);
                result.append("商品：" + skuLockVo.getSkuId() + " 库存不足;");
            }
            //锁定失败，解除
            redisTemplate.delete(key);
            return result.toString();
        } else { //都有库存，可以锁定库存。
            //4.所有商品都存在库存，可以锁定
            for (SkuLockVo skuLockVo : skuLockVoList) {
                //修改商品lock_num  available_num
                int row = skuStockMapper.lock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
                if (row == 0) {
                    redisTemplate.delete(key);
                    throw new ServiceException("锁定库存失败");
                }
            }
            //redisTemplate.delete(key); //1个小时后自动删除。
        }
        //5.将订单编号作为缓存key,锁定库存信息作为值。保存到redis中。便于后期  解锁库存 或 减库存   二选一。
        redisTemplate.opsForValue().set(dataKey, skuLockVoList);
        return "";
    }

    @Transactional
    @Override
    public void unlock(String orderNo) {
        String key = "sku:unlock:" + orderNo;
        String dataKey = "sku:lock:data:" + orderNo;
        //业务去重，防止重复消费
        Boolean isExist = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if(!isExist) return;

        // 获取锁定库存的缓存信息
        List<SkuLockVo> skuLockVoList = (List<SkuLockVo>)this.redisTemplate.opsForValue().get(dataKey);
        if (CollectionUtils.isEmpty(skuLockVoList)){
            return ;
        }

        // 解锁库存
        skuLockVoList.forEach(skuLockVo -> {
            int row = skuStockMapper.unlock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
            if(row == 0) {
                //解除去重
                this.redisTemplate.delete(key);
                throw new ServiceException("解锁出库失败");
            }
        });

        // 解锁库存之后，删除锁定库存的缓存。以防止重复解锁库存
        this.redisTemplate.delete(dataKey);   //  TODO  这行可以删除吗？ 不删       解锁库存和减库存  两个操作二选一。
    }


    @Override
    public void minus(String orderNo) {
        String key = "sku:minus:" + orderNo;
        String dataKey = "sku:lock:data:" + orderNo;
        //业务去重，防止重复消费
        Boolean isExist = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if(!isExist) return;

        // 获取锁定库存的缓存信息
        List<SkuLockVo> skuLockVoList = (List<SkuLockVo>)this.redisTemplate.opsForValue().get(dataKey);
        if (CollectionUtils.isEmpty(skuLockVoList)){
            return ;
        }

        // 减库存
        skuLockVoList.forEach(skuLockVo -> {
            int row = skuStockMapper.minus(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
            if(row == 0) {
                //解除去重
                this.redisTemplate.delete(key);
                throw new ServiceException("减出库失败");
            }
        });

        // 减库存之后，删除锁定库存的缓存。以防止重复减库存
        this.redisTemplate.delete(dataKey);
    }
}